#                                         _ooOoo_
#                                        o8888888o
#                                        88" . "88
#                                        (| -_- |)
#                                         O\ = /O
#                                     ____/`---'\____
#                                    .  ' \\| |// `  .
#                                    / \\||| : |||// \
#                                  / _||||| -:- |||||- \
#                                 |  | | \\\ - /// | |  |
#                                 |  \_| ''\---/'' |_/  |
#                                 \   .-\__ `-` ___/-.  /
#                                ___`. .' /--.--\ `. . ___
#                             ."" '< `.___\_<|>_/___.' >' "".
#                            | | : `- \`.;`\ _ /`;.`/ - ` : | |
#                            \ \ `-.   \_ __\ /__ _/    .-` / /
#                      ======`-.____`-.___\_____/___.-`____.-'======
#                                         `=---='
#                      .............................................
#                             佛祖保佑             永无BUG

import json
import numbers
import os
from time import sleep

import pandas as pd
import requests
import xlwings as xl
from dotenv import find_dotenv, load_dotenv

load_dotenv(find_dotenv())

LARK_HOST='https://open.feishu.cn'
BITABLE_PATH = '/open-apis/bitable/v1/apps'
APP_ID=os.getenv('APP_ID')
APP_SECRET=os.getenv('APP_SECRET')


class Collection:
    def __init__(self,impl) -> None:
        self.impl = impl

    def __call__(self, name_or_index):
        if isinstance(name_or_index,self._wrap):
            return name_or_index
        elif isinstance(name_or_index,numbers.Number):
            return self._wrap(
                app_token=self.app_token,
                impl=self.impl[name_or_index - 1]
            )
    
    def __len__(self):
        return len(self.impl)
    
    @property
    def count(self):
        return len(self)
    
    def __iter__(self):
        for impl in self.impl:
            yield self._wrap(impl=impl) 

    def __getitem__(self,key):
        if isinstance(key,numbers.Number):
            l = len(self)
            if key >= l:
                raise IndexError("Index %s out of range (%s elements)" % (key, l))
            if key < 0:
                if key < -l:
                    raise IndexError("Index %s out of range (%s elements)" % (key, l))
                key += l
            return self(key + 1)
        elif isinstance(key, slice):
            raise ValueError(
                self.impl.__class__.__name__ +  "object does not support slicing"
            )
        else:
            return self(key)

    @property
    def _name(self):
        return self.__class__.__name__

    def __repr__(self) -> str: 
        r = []
        for i,x in enumerate(self):
            if i == 3:
                r.append('...')
                break
            else:
                r.append(repr(x))
        return f'{self._name}([{", ".join(r)}])'

class App:

    app_id = APP_ID
    app_secret = APP_SECRET

    @property
    def access_token(self):
        url = LARK_HOST + '/open-apis/auth/v3/app_access_token/internal'
        headers={
            'Content-Type':'application/json; charset=utf-8'
        }
        plyload={'app_id':self.app_id,'app_secret':self.app_secret}
        token = requests.request('POST',url,headers=headers,json=plyload)
        if token.text == "404 page not found":
            raise ValueError(token)
        token = json.loads(token.text)

        if token['code']:
            print(token)
            return None
        else:
            return token['app_access_token']
    
    @property
    def headers(self):
        return {
            "Content-Type":"application/json; charset=utf-8",
            "Authorization" : "Bearer " + self.access_token
        }
    
    def bitable(self, app_token):
        return Bitable(app_token=app_token)

class Bitable:

    page_size = 20

    def __init__(self, app_token=None) -> None:
        self.app_token = app_token

    @property
    def app(self):
        return app
        
    @property
    def tables(self):
        def select_tables(has_more=False, page_token=None)->list:
            url = LARK_HOST + BITABLE_PATH + f"/{self.app_token}/tables?page_size={self.page_size}"
            if has_more:
                url = url + f'&page_token={page_token}'
            response = json.loads(requests.request('GET',url,headers=app.headers).text)

            staus = response.get('code',None)
            if staus:
                return {staus:response.get('msg')}
            elif staus == 0:
                response = response['data']
                has_more = response['has_more']
                if has_more:
                    return select_tables(has_more,response['page_token']) + response['items']
                else:
                    return response['items']

        tables = select_tables()
        if isinstance(tables,list):
            return Tables(self.app_token, tables)
        elif isinstance(tables,dict):
            return tables
    
    def __repr__(self):
        return '<Bitable [{}]'.format(self.app_token)

class Bitables(Collection):

    _wrap = Bitable

    def __call__(self, name_or_index):
        if isinstance(name_or_index,Bitable):
            return name_or_index
        else:
            return Bitable(impl=self.impl(name_or_index))

    def add(self):
        pass

    def select(self,app_token):
        return Bitable(app_token=app_token)


class Table:

    page_size = 300
    timesleep = 0.01
    
    def __init__(self,app_token=None,table_id=None,impl=None) -> None:
        self.app_token = app_token
        if not impl:
            self._table_id = table_id
        else:
            self._table_id = impl['table_id']
        self.impl = impl

    @property
    def name(self):
        # return self.impl['name']
        return "<-name->"

    @property
    def table_id(self): 
        return self._table_id
    
    @property
    def fields(self):
        # 获取字段信息
        pass

    @property
    def records(self):
        def select_records(has_more=False,page_token=None):
            url = LARK_HOST + BITABLE_PATH + f'/{self.app_token}/tables/{self.table_id}/records?page_size={self.page_size}'
            if has_more:
                url = url + f'&page_token={page_token}'
            response = json.loads(requests.request('GET',url=url,headers=app.headers).text)

            staus = response.get('code',None)
            if staus:
                return {staus:response.get('msg')}
            elif staus == 0:
                response = response['data']
                has_more = response['has_more']
                _items = response['items']
                if has_more:
                    return select_records(has_more,response['page_token']) + _items
                else:
                    return _items
        return select_records()
        # records = select_records()
        # if isinstance(records,list):
        #     return Records(records)
        # elif isinstance(records,dict):
        #     return records

    @property
    def value(self)->pd.DataFrame:
        return 
    
    @value.setter
    def value(self,data:pd.DataFrame):
        def create(data:pd.DataFrame,data_next=None):
            if not data.empty or data_next:
                print(f'剩余{data.shape[0]}条记录待上传')
                frame_pop , fram_next = data.iloc[:self.page_size,:], data.iloc[self.page_size:,:]

                url = LARK_HOST + BITABLE_PATH + f'/{self.app_token}/tables/{self.table_id}/records/batch_create'
                plyload = json.dumps({
                    'records':[ 
                        {'fields':record} 
                        for record in json.loads(
                            frame_pop.to_json(orient='records',force_ascii=False)
                        )
                    ]
                })
                response = requests.request('POST',url,headers=app.headers,data=plyload)
                response_text = json.loads(response.text)

                if response_text['code']:
                    print('新增错误:',response_text)
                    sleep(self.timesleep)
                    create(data=data)
                else:
                    create(data=fram_next)
        create(data)
    
    def clear(self):
        get_url = LARK_HOST + BITABLE_PATH + f'/{self.app_token}/tables/{self.table_id}/records?page_size={self.page_size}'
        response = json.loads(requests.request('GET',url=get_url,headers=app.headers).text)
        staus = response.get('code',None)
        if staus:
            print('获取记录错误:',response)
            if staus == '1254607':
                sleep(0.1)
            self.clear()
        elif staus == 0 :
            records = response.get('data',None).get('items',None)
            if records is None:
                print('无记录或已全部删除')
            else: #调用成功且有数据则删除记录
                has_more = response['data'].get('has_more',False)

                rm_url = LARK_HOST + BITABLE_PATH + f'/{self.app_token}/tables/{self.table_id}/records/batch_delete'
                payload = json.dumps({
                    'records':list( map( lambda x:x['record_id'], records ) )
                })
                response = json.loads(requests.request("POST",rm_url, headers=app.headers, data=payload).text)
                staus = response.get('code',None)
                if staus: # 删除出错继续调用，还有其他数据继续调用
                    print('删除错误：',response)
                    self.clear()
                else:
                    print(f'删除{len(records)}条记录')
                    if has_more:
                        sleep(0.005)
                        self.clear()
                    else:
                        print('已全部删除')

    @property
    def bitable(self):
        return Bitable(self.app_token)

    def __repr__(self):
        return f'<Table [{self.name}]'


class Tables(Collection):

    _wrap = Table

    def __init__(self, app_token, impl) -> None:
        super().__init__(impl)
        self.app_token = app_token

    def __iter__(self):
        for impl in self.impl:
            yield Table(self.app_token,impl=impl)
    
    def select(self,table_id):
        for impl in self.impl:
            if impl['table_id'] == table_id:
                return Table(self.app_token,table_id)


class Record:
    pass

class Rocrods(Collection):
    def __init__(self, impl) -> None:
        super().__init__(impl)

    @property
    def records_ids(self):
        return 

    def __repr__(self) -> str:
        return super().__repr__()

    def __str__(self) -> str:
        return super().__str__()


app = App()
